{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5.3 变量管理\n",
    "在5.2.1节中将计算神经网络前向传播结果的过程抽象成了一个函数，这样在训练和测试的过程中可以统一调用同一个函数来得到模型的前向传播结果，如下：\n",
    "\n",
    "`def inference(input_tensor, avg_class, weights, biases1, weights2, biases2)：`\n",
    "\n",
    "可以看到，这个函数中包括了神经网络中的所有参数，然而当神经网络的结构更加复杂、参数更多时，就需要一个更好的方式来传递和管理神经网络中的参数。**TensorFlow提供了通过变量名称来创建或者获取一个变量的机制，通过这个机制，在不同函数中可以直接通过变量的名字来获取变量，而不需要将变量通过参数的形式到处传递。这主要是通过 `tf.get_variable` 和 `tf.variable_scope` 函数实现的，**下面分别介绍如何使用这两个函数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**1. tf.get_variable**\n",
    "\n",
    "第4章中介绍了通过 `tf.Variable` 函数来创建一个变量，除此之外，TF还提供了 `tf.get_variable` 来创建或者获取变量，这两者功能基本等价，样例如下：\n",
    "\n",
    "`v = tf.Variable(tf.constant(1.0, shape=[1]), name='v')\n",
    "v = tf.get_variable('v', shape=[1], initializer=tf.constant_initializer(1.0))`\n",
    "\n",
    "可以看到，这两个过程基本一样，TF提供的initializer函数和3.4.3节中介绍的随机数及常量生成函数大部分是一一对应的，TF提供的7种不同初始化函数如下：\n",
    "<p align='center'>\n",
    "    <img src=images/表5.2.JPG>\n",
    "</p>\n",
    "\n",
    "**`tf.Variable` 函数和 `tf.get_variable` 函数最大的区别在于指定变量名称的参数：**\n",
    "- **对于`tf.Variable` ，变量名称是可选的参数**，通过 name='v' 形式给出；\n",
    "- **对于`tf.get_variable`，变量名称是必填的参数**，它会根据这个名字去创建或者获取变量，对于上例，它首先会去创建名为v的参数，如果创建失败（如已有同名参数）则会报错，这是为了避免无意识的变量复用造成的错误，比如神经网络中不同层的权重都命名为weights，否则不同层共用一个权重会出现一些难以发现的错误。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**2. tf.variable_scope**\n",
    "\n",
    "**如果需要通过 `tf.get_variable` 来获取一个已经创建的变量，需要通过 `tf.variable_scope` 函数来生成一个上下文管理器，并明确指定在这个上下文管理器中，`tf.get_variable`将直接获取已经生成的变量。**下面是一段说明通过 `tf.variable_scope` 函数可以控制 `tf.get_variable` 函数语义的样例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "# 在名为foo的命名空间内创建名为v的变量\n",
    "with tf.variable_scope(\"foo\"):\n",
    "    v = tf.get_variable(\"v\", [1], initializer=tf.constant_initializer(1.0))\n",
    "\n",
    "# 以下代码会报错（ValueError: Variable foo/v already exists, disallowed.），因为该空间已存在同名变量\n",
    "# with tf.variable_scope(\"foo\"):\n",
    "#     v = tf.get_variable(\"v\", [1])\n",
    "\n",
    "# 在生成上下文管理器时，将参数reuse设置为True，这样tf.get_varibale函数将直接获取已经生命的变量\n",
    "with tf.variable_scope(\"foo\", reuse=True):\n",
    "    v1 = tf.get_variable(\"v\", [1])\n",
    "print(v == v1)\n",
    "\n",
    "# 将参数reuse设置为True时，tf.variavle_scope将只能获取已经创建过的变量，否则也会报错\n",
    "# with tf.variable_scope(\"bar\", reuse=True):\n",
    "#    v = tf.get_variable(\"v\", [1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，当 `tf.variable_scope` ：\n",
    "- 使用参数 reuse=None或reuse=False 创建上下文管理器，`tf.get_variable` 操作将创建新的变量，如果存在同名变量则报错；\n",
    "- 使用参数 reuse=True 创建上下文管理器，这个管理器内所有 `tf.get_variable` 操作将直接获取已创建变量，如果变量不存在则报错；\n",
    "\n",
    "**TF中 `tf.variable_scope` 函数是可以嵌套的**，下面程序说明了当嵌套时，reuse的取值时如何确定的："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n",
      "True\n",
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "with tf.variable_scope(\"root\"):\n",
    "    # 可以通过tf.get_variable_scope().reuse来获取当前上下文管理器中reuse参数的取值\n",
    "    print(tf.get_variable_scope().reuse)            # 输出False，即最外层reuse是默认的False\n",
    "    \n",
    "    # 建立一个嵌套的上下文管理器\n",
    "    with tf.variable_scope(\"foo\", reuse=True):\n",
    "        print(tf.get_variable_scope().reuse)        # 输出True，即foo空间的reuse取值\n",
    "        \n",
    "        # 再建立一个嵌套的上下文管理器\n",
    "        with tf.variable_scope(\"bar\"):\n",
    "            print(tf.get_variable_scope().reuse)    # 输出True，这时若不指定则会和外层保持一致\n",
    "            \n",
    "    print(tf.get_variable_scope().reuse)            # 输出False，即回到最外层"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**`tf.variable_scope` 函数生成的上下文管理器也会创建了一个TF中的命名空间，在命名空间内创建的变量名称都会带上这个命名空间作为前缀。所以，`tf.variable_scope` 函数除了可以控制 `tf.get_variable` 执行的功能，也提供了一个管理变量命名空间的方法**。在某个命名空间下，可以直接通过带命名空间名称的变量名来获取其他命名空间下的变量。以下代码显示了如何通过 `tf.variable_scope` 来管理变量的名称："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "v:0\n",
      "foo/v:0\n",
      "foo/bar/v:0\n",
      "foo/v1:0\n",
      "True\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "v1 = tf.get_variable(\"v\", [1])\n",
    "print(v1.name)                                   # 输出v:0，'v'表示变量名，'0'表示这个变量是生成变量这个运算的第一个结果\n",
    "\n",
    "with tf.variable_scope(\"foo\", reuse=True):\n",
    "    v2 = tf.get_variable(\"v\", [1])\n",
    "    print(v2.name)                               # 输出foo/v:0，TF中用/来分隔命名空间的名称和变量的名称\n",
    "\n",
    "with tf.variable_scope(\"foo\"):\n",
    "    with tf.variable_scope(\"bar\"):\n",
    "        v3 = tf.get_variable(\"v\", [1])\n",
    "        print(v3.name)                           # 输出foo/bar/v:0，命名空间可以嵌套，变量名称也会加入嵌套的命名空间\n",
    "        \n",
    "    v4 = tf.get_variable(\"v1\", [1])\n",
    "    print(v4.name)                               # 输出foo/v1:0，命名空间推出后，变量名称也不会再将其加入前缀\n",
    "\n",
    "# 可以直接通过带命名空间名称的变量名来获取其他命名空间下的变量, 创建一个命名为空命名空间，并设置reuse=True\n",
    "with tf.variable_scope(\"\", reuse=True):\n",
    "    v5 = tf.get_variable(\"foo/bar/v\", [1])\n",
    "    print(v5 == v3)                              # 输出为True\n",
    "    \n",
    "    v6 = tf.get_variable(\"foo/v1\")\n",
    "    print(v6 == v4)                              # 输出为True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "最后，通过使用 `tf.variable_scope` 和 `tf.get_variable` 函数，可以对5.2.1中定义的计算前向传播的函数做出一些改进。如下所示，这样就不再需要将所有的变量都作为参数传递到不同的函数中了，当神经网络复杂、参数更多时，使用这种变量管理方式将大大提高程序的可读性。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "def inference(input_tensor, reuse=False):\n",
    "    # 定义第一层神经网络的变量和前向传播过程\n",
    "    with tf.variable_scope('layer1', reuse=reuse):\n",
    "        # 根据传进来的reuse判断创建新变量还是使用已创建好的。第一次构造网络时需要创建新的，以后每次调用都使用reuse=True就不需要每次传递变量进来\n",
    "        weights = get_weight_variable([INPUT_NODE, LAYER1_NODE], initializer=tf.truncated_normal_initializer(stddev=0.1))\n",
    "        biases = tf.get_variable(\"biases\", [LAYER1_NODE], initializer=tf.constant_initializer(0.0))\n",
    "        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)\n",
    "\n",
    "    # 定义第二层神经网络的变量和前向传播过程\n",
    "    with tf.variable_scope('layer2', reuse=reuse):\n",
    "        weights = get_weight_variable([LAYER1_NODE, OUTPUT_NODE], initializer=tf.truncated_normal_initializer(stddev=0.1))\n",
    "        biases = tf.get_variable(\"biases\", [OUTPUT_NODE], initializer=tf.constant_initializer(0.0))\n",
    "        layer2 = tf.matmul(layer1, weights) + biases\n",
    "\n",
    "    # 返回最后的前向传播结果\n",
    "    return layer2\n",
    "\n",
    "x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')\n",
    "y = inference(x)\n",
    "\n",
    "# 如果在程序中需要使用训练好的神经网络进行推倒时，可以直接调用inference(new_x, True)\n",
    "# 如果需要使用滑动平均模型，可以参考5.2.1中代码，把计算滑动平均的类传到inference函数中即可，创建或获取变量的部分不需要改变\n",
    "new_x = ...\n",
    "new_y = inference(new_x, True)\n",
    "'''"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
